package com.forest.datachangetracker.service.audit;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.forest.datachangetracker.annotation.DataAudit;
import com.forest.datachangetracker.context.AuditContext;
import com.forest.datachangetracker.dto.EntityChange;
import com.forest.datachangetracker.enums.OperationType;
import com.forest.datachangetracker.utils.EntityIdExtractor;
import com.forest.datachangetracker.utils.EntityValueExtractor;
import org.javers.core.Javers;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * 数据审计策略.
 *
 * @author forest
 */
public interface AuditStrategy {

    /**
     * 是否支持该操作类型
     *
     * @param dataAudit
     * @return true/false
     */
    boolean supports(DataAudit dataAudit);

    /**
     * 执行审计策略
     *
     * @param context   上下文信息
     * @param result    方法返回对象
     * @param dataAudit 数据审计注解
     * @param oldEntity 原始数据实例
     */
    void execute(AuditContext context, Object result, DataAudit dataAudit, Object oldEntity);


    /**
     * 生成批量操作ID
     *
     * @param context
     * @param dataAudit
     * @return
     */
    default String generateBatchId(AuditContext context, DataAudit dataAudit) {
        if (!dataAudit.batchId().isEmpty()) {
            return context.getSpelParser().parseExpression(dataAudit.batchId())
                    .getValue(context.getEvaluationContext(), String.class);
        }
        return "batch_" + IdWorker.getIdStr();
    }

    /**
     * 处理变更
     *
     * @param context
     * @param newEntity
     * @param oldEntity
     * @param dataAudit
     */
    default void processChanges(AuditContext context, Object newEntity, Object oldEntity, DataAudit dataAudit) {
        // 从结果中提取ID
        String targetId = extractTargetId(context, dataAudit, ObjectUtil.equals(dataAudit.operationType(), OperationType.DELETE) ? oldEntity : newEntity);
        context.setTargetId(targetId);
        String batchId = generateBatchId(context, dataAudit);
        context.setBatchId(batchId);
        // 提取新实体值
        EntityChange change = new EntityChange();
        if(ObjectUtil.isNotNull(newEntity)) {
            change.setNewValues(new HashMap<>());
        }
        if(ObjectUtil.isNotNull(oldEntity)) {
            change.setOldValues(new HashMap<>());
        }
        change.setChangedFields(new HashMap<>());
        // 比较新旧实体差异
        Javers javers = SpringUtil.getBean(Javers.class);
        Diff diff = javers.compare(oldEntity, newEntity);
        List<String> fieldNames = EntityValueExtractor.extractEntityFieldNames(dataAudit.targetEntity());
        // 处理变更
        diff.getChangesByType(ValueChange.class).forEach(valueChange -> {
            boolean ignoreField = (dataAudit.ignoreNullChanges() && valueChange.getLeft() == null && valueChange.getRight() == null) || !fieldNames.contains(valueChange.getPropertyName());
            if (ignoreField) {
                return;
            }
            String fieldName = valueChange.getPropertyName();
            if (ObjectUtil.isNotNull(change.getOldValues())) {
                change.getOldValues().put(fieldName, valueChange.getLeft());
            }
            if (ObjectUtil.isNotNull(change.getNewValues())) {
                change.getNewValues().put(fieldName, valueChange.getRight());
            }
            String  operationType = "MODIFIED";
            if(ObjectUtil.isNull(valueChange.getLeft()) && ObjectUtil.isNotNull(valueChange.getRight())) {
                operationType = "ADDED";
            } else if(ObjectUtil.isNotNull(valueChange.getLeft()) && ObjectUtil.isNull(valueChange.getRight())) {
                operationType = "DELETED";
            }
            change.getChangedFields().put(fieldName, createChangeInfo(valueChange, operationType));
        });
        // 记录审计日志
        if(dataAudit.async()) {
            CompletableFuture.runAsync(() -> {
                context.getAuditLogService().recordAuditLog(
                        context, change.getOldValues(), change.getNewValues(),
                        change.getChangedFields(), true, null
                );
            });
        } else {
            context.getAuditLogService().recordAuditLog(
                    context, change.getOldValues(), change.getNewValues(),
                    change.getChangedFields(), true, null
            );
        }
    }

    default Object createChangeInfo(ValueChange change, String operationType) {
        return new HashMap<String, Object>() {{
            put("oldValue", change.getLeft());
            put("newValue", change.getRight());
            put("changeType", operationType);
            put("property", change.getPropertyName());
        }};
    }

    /**
     * 从结果中提取ID
     *
     * @param context
     * @param dataAudit
     * @param newEntity
     * @return
     */
    default String extractTargetId(AuditContext context, DataAudit dataAudit, Object newEntity) {
        // 优先从resultId表达式获取
        if (!dataAudit.resultId().isEmpty()) {
            String resultId = context.getSpelParser().parseExpression(dataAudit.resultId())
                    .getValue(context.getEvaluationContext(), String.class);
            if (resultId != null) {
                return resultId;
            }
        }
        // 从实体中提取ID
        return EntityIdExtractor.extractId(newEntity);
    }
}
